package cc.rengu.oltp.utility.util;

import cc.rengu.oltp.utility.util.sm2.Sm2Engine;
import cc.rengu.oltp.utility.util.sm2.Sm2Signer;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;

import java.io.*;
import java.math.BigInteger;

/**
 * SM2工具类 on 2019/11/27.
 */
public class SM2Util {

    /**
     * 椭圆曲线y2 = x3 + ax + b系统参数： q: a b G(x,y) n
     */
    /**
     * 有限域Fq中的元素数目
     */
    private static BigInteger q;
    /**
     * Fq中的元素，定义一条椭圆曲线
     */
    private static BigInteger a;
    private static BigInteger b;
    /**
     * 基点G的阶
     */
    private static BigInteger n;
    /**
     * 椭圆曲线的一个基点，其阶为素数
     */
    private static BigInteger xG;
    private static BigInteger yG;
    private static ECPoint G;

    private static ECCurve curve;
    private static ECDomainParameters ec;

    static {
        q = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16);
        a = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16);
        b = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16);
        n = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16);
        xG = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
        yG = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);

        curve = new ECCurve.Fp(q, a, b);
        G = createPoint(xG, yG);
        ec = new ECDomainParameters(curve, G, n);
    }

    /**
     * 根据坐标获得椭圆曲线上的点
     *
     * @param x x坐标
     * @param y y坐标
     * @return 点
     */
    @SuppressWarnings({"deprecation", "AliDeprecation"})
    public static ECPoint createPoint(BigInteger x, BigInteger y) {
        ECFieldElement ecc_gx_fieldelement;
        ECFieldElement ecc_gy_fieldelement;

        ecc_gx_fieldelement = new ECFieldElement.Fp(q, x);
        ecc_gy_fieldelement = new ECFieldElement.Fp(q, y);
        return new ECPoint.Fp(curve, ecc_gx_fieldelement, ecc_gy_fieldelement);
    }

    /**
     * 转为指定bit(bit/8)的byte[]
     *
     * @param val
     * @param bitLength
     * @return
     */
    public static byte[] bigIntegerToByteArray(BigInteger val, int bitLength) {
        int len = val.toByteArray().length;
        int fixedLength = bitLength / 8;
        if (len == fixedLength) {
            return val.toByteArray();
        }
        byte[] tmp = new byte[fixedLength];
        if (len < fixedLength) {
            System.arraycopy(val.toByteArray(), 0, tmp, fixedLength - len, len);
        }
        if (len > fixedLength) {// 截断
            System.arraycopy(val.toByteArray(), len - fixedLength, tmp, 0, fixedLength);
        }
        return tmp;
    }

    /**
     * 转为256bit（32byte）的byte[]
     *
     * @param big
     * @return
     */
    public static byte[] bigIntegerToByteArray(BigInteger big) {
        return bigIntegerToByteArray(big, 256);
    }

    /// 生成SM2私钥
    public static ECPrivateKeyParameters sm2PriKeyGet(String dH) {
        BigInteger d = new BigInteger(dH, 16);
        return new ECPrivateKeyParameters(d, ec);
    }

    /// 生成SM2私钥
    public static ECPrivateKeyParameters sm2PriKeyGet(byte[] dH) {
        BigInteger d = new BigInteger(1, dH);
        return new ECPrivateKeyParameters(d, ec);
    }

    /// 生成SM2公钥
    public static ECPublicKeyParameters sm2PubKeyGet(String xH, String yH) {
        BigInteger qx = new BigInteger(xH, 16);
        BigInteger qy = new BigInteger(yH, 16);
        ECPoint q = createPoint(qx, qy);
        return new ECPublicKeyParameters(q, ec);
    }

    public static ECPublicKeyParameters sm2PubKeyGet(byte[] xH, byte[] yH) {
        BigInteger qx = new BigInteger(1, xH);
        BigInteger qy = new BigInteger(1, yH);
        ECPoint q = createPoint(qx, qy);
        return new ECPublicKeyParameters(q, ec);
    }

    /**
     * 根据私钥d获取公钥P
     *
     * @param d 私钥d
     * @return 公钥P
     */
    public static ECPoint GetPublicKey(BigInteger d) {
        /**
         * 加解密基于数学难题，K=kG，其中 K,G为Ep(a,b)上的点，k是整数，小于G点的阶
         * 给定k和G，计算K很容易；但给定K和G，求k就困难了。这样，G就叫做基点，k是私钥，K是公钥
         * 公钥是曲线在离散坐标系中，满足条件的一个曲线上的点。
         */
        return G.multiply(d);
    }

    /**
     * SM2 公钥加密
     *
     * @param param
     * @param data
     * @return
     */
    public static byte[] Encrypt(CipherParameters param, byte[] data) {
        Sm2Engine engine = new Sm2Engine();
        byte[] result = null;
        try {
            engine.init(true, param);
            result = engine.processBlock(data, 0, data.length);
        } catch (Exception e) {

            return null;
        }
        return result;
    }

    /**
     * SM2 私钥解密
     *
     * @param priKey
     * @param data
     * @return
     */
    public static byte[] Decrypt(CipherParameters priKey, byte[] data) {
        Sm2Engine engine = new Sm2Engine();
        byte[] result = null;
        try {
            engine.init(false, priKey);
            result = engine.processBlock(data, 0, data.length);
        } catch (Exception e) {

            return null;
        }
        return result;
    }

    /**
     * SM2 私钥签名
     *
     * @param param
     * @param signSn
     * @param msg
     * @return
     */
    public static byte[] SignWithSm3(CipherParameters param, byte[] signSn, byte[] msg) {
        CipherParameters priKey = param;
        // 先做一次Hash
        if (priKey instanceof ParametersWithRandom) {
            ParametersWithRandom rParam = (ParametersWithRandom) param;
            priKey = rParam.getParameters();
        }

        if (!(priKey instanceof ECPrivateKeyParameters)) {

            System.out.println("EC private key required for signing");
            return null;
        }
        // e=Hv(M_), M_=ZA||M
        byte[] za = null;
        za = CalculateZA((ECPrivateKeyParameters) priKey, signSn);

        SM3Digest digest = new SM3Digest();
        byte[] e = new byte[digest.getDigestSize()];
        digest.update(za, 0, za.length);
        digest.update(msg, 0, msg.length);
        digest.doFinal(e, 0);

        // 签名,e 为 Hv(ZA||M)
        BigInteger[] sig;
        Sm2Signer signer = new Sm2Signer();
        try {
            signer.init(true, param);
            sig = signer.generateSignature(e);
        } catch (Exception ex) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ex.printStackTrace(new PrintWriter(byteArrayOutputStream, true));
            return null;
        }
        // 拼接RS
        byte[] signature;
        signature = Sm2Engine.combineByteArray(bigIntegerToByteArray(sig[0]), bigIntegerToByteArray(sig[1]));

        return signature;
    }

    /**
     * SM2 公钥验签
     *
     * @param pubKey
     * @param signSn
     * @param msg
     * @param sign
     * @return
     */
    public static boolean VerifyWithSm3(CipherParameters pubKey, byte[] signSn, byte[] msg, byte[] sign) {
        // e=Hv(M_), M_=ZA||M
        byte[] za = null;
        za = CalculateZA((ECPublicKeyParameters) pubKey, signSn);
        SM3Digest digest = new SM3Digest();
        byte[] e = new byte[digest.getDigestSize()];
        digest.update(za, 0, za.length);
        digest.update(msg, 0, msg.length);
        digest.doFinal(e, 0);

        BigInteger r;
        BigInteger s;

        InputStream is = new ByteArrayInputStream(sign);
        byte[] rb = new byte[sign.length / 2];
        byte[] sb = new byte[sign.length / 2];
        try {
            is.read(rb, 0, sign.length / 2);
            is.read(sb, 0, sign.length / 2);
        } catch (IOException e1) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            e1.printStackTrace(new PrintWriter(byteArrayOutputStream, true));
            ;
            return false;
        }
        r = new BigInteger(1, rb);
        s = new BigInteger(1, sb);

        boolean result = false;
        Sm2Signer signer = new Sm2Signer();
        try {
            signer.init(false, pubKey);
            result = signer.verifySignature(e, r, s);
        } catch (Exception ex) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ex.printStackTrace(new PrintWriter(byteArrayOutputStream, true));
        }
        return result;
    }

    /**
     * ZA: 用户的标志、部分椭圆曲线参数、用户公钥的 杂凑值
     *
     * @param G
     * @param a
     * @param b
     * @param A
     * @param id
     * @return
     */
    private static byte[] CalculateZA(ECPoint G, BigInteger a, BigInteger b, ECPoint A, byte[] id) {
        SM3Digest digest = new SM3Digest();
        byte[] e = new byte[digest.getDigestSize()];
        byte[] tmp = null;

        // ENTLA
        int entla = id.length * 8;
        digest.update((byte) ((entla >> 8) & 0xFF));
        digest.update((byte) (entla & 0xFF));

        // IDA
        digest.update(id, 0, id.length);

        // a
        tmp = bigIntegerToByteArray(a);
        digest.update(tmp, 0, tmp.length);

        // b
        tmp = bigIntegerToByteArray(b);
        digest.update(tmp, 0, tmp.length);

        // xG
        tmp = bigIntegerToByteArray(G.getAffineXCoord().toBigInteger());
        digest.update(tmp, 0, tmp.length);

        // yG
        tmp = bigIntegerToByteArray(G.getAffineYCoord().toBigInteger());
        digest.update(tmp, 0, tmp.length);

        // xA
        tmp = bigIntegerToByteArray(A.getAffineXCoord().toBigInteger());
        digest.update(tmp, 0, tmp.length);

        // yA
        tmp = bigIntegerToByteArray(A.getAffineYCoord().toBigInteger());
        digest.update(tmp, 0, tmp.length);

        digest.doFinal(e, 0);
        return e;
    }

    public static byte[] CalculateZA(ECPrivateKeyParameters priKey, byte[] id) {
        ECPoint G = priKey.getParameters().getG();
        BigInteger a = priKey.getParameters().getCurve().getA().toBigInteger();
        BigInteger b = priKey.getParameters().getCurve().getB().toBigInteger();

        ECPoint A = ECAlgorithms.referenceMultiply(G, priKey.getD()).normalize();

        return CalculateZA(G, a, b, A, id);
    }

    public static byte[] CalculateZA(ECPublicKeyParameters pubKey, byte[] id) {

        ECPoint G = pubKey.getParameters().getG();
        BigInteger a = pubKey.getParameters().getCurve().getA().toBigInteger();
        BigInteger b = pubKey.getParameters().getCurve().getB().toBigInteger();

        ECPoint A = pubKey.getQ();

        return CalculateZA(G, a, b, A, id);
    }


    /**
     * SM2私钥签名
     * <p>
     * 过程说明：先对报文中<root></root>标签中的所有内容（包括<root>和</root>标签自身）
     * 生成SM3摘要，再对摘要进行SM2签名，最后对生成的签名进行base64编码
     *
     * @param priKey 签名私钥
     * @param signSn 证书序列号
     * @param msg    待签名消息
     * @return base64后的签名结果
     */
    public static String sm2SignWithSm3(String priKey, byte[] signSn, byte[] msg) {
        byte[] pri = Base64.decodeBase64(priKey);
        CipherParameters privateKey = sm2PriKeyGet(Hex.encodeHexString(pri));

        String result;
        // 数据摘要
        byte[] sm3Msg = SM3Util.digest(msg);

        // 签名
        byte[] sign = SignWithSm3(privateKey, signSn, sm3Msg);

        // base64
        result = Base64.encodeBase64String(sign);

        return result;
    }


    /**
     * SM2公钥验签
     * <p>
     * 过程说明：先对报文中<root></root>标签中的所有内容（包括<root>和</root>标签自身）
     * 生成SM3摘要，然后将base64编码后的签名数据进行base64解码， 最后使用摘要和base64解码后的签名进行SM2验签
     *
     * @param pubKey 验签公钥
     * @param signSn 证书序列号
     * @param msg    被签名消息
     * @param signB  待验证签名
     * @return 验签结果：true(验签成功) 或 false(验签失败)
     */
    public static boolean sm2VerifyWithSm3(String pubKey, byte[] signSn, byte[] msg, String signB) {
        boolean result;

        CipherParameters publicKey = getPubKeyFPubCert(pubKey);
        //生成SM3摘要
        byte[] sm3Msg = SM3Util.digest(msg);
        //Base64解码
        byte[] sign = Base64.decodeBase64(signB);

        result = VerifyWithSm3(publicKey, signSn, sm3Msg, sign);

        return result;
    }


    /**
     * 从公钥证书中获取公钥
     *
     * @param pubKey
     * @return 公钥参数对象
     */
    private static CipherParameters getPubKeyFPubCert(String pubKey) {
        CipherParameters cipherParameters = null;
        try {

            byte[] csCert = Base64.decodeBase64(pubKey);
            Certificate cert = certFrom(csCert);


            SubjectPublicKeyInfo subjectPublicKeyInfo = cert.getSubjectPublicKeyInfo();
            DERBitString publicKeyData = subjectPublicKeyInfo.getPublicKeyData();
            byte[] publicKey = publicKeyData.getEncoded();
            byte[] encodedPublicKey = publicKey;
            byte[] ecP = new byte[64];
            System.arraycopy(encodedPublicKey, 4, ecP, 0, ecP.length);

            byte[] certPKX = new byte[32];
            byte[] certPKY = new byte[32];
            System.arraycopy(ecP, 0, certPKX, 0, 32);
            System.arraycopy(ecP, 32, certPKY, 0, 32);
            cipherParameters = SM2Util.sm2PubKeyGet(certPKX, certPKY);
        } catch (Exception e) {

        }
        return cipherParameters;
    }

    private static final Certificate certFrom(byte[] certData) throws Exception {
        Certificate cert = null;
        try {
            ASN1Sequence seq = ASN1Sequence.getInstance(certData);
            cert = Certificate.getInstance(seq);
        } catch (Exception ex) {
            throw new Exception(ex);
        }

        return cert;
    }

    private static byte[] getPublicKey(Certificate cert) {
        byte[] pubData = cert.getSubjectPublicKeyInfo().getPublicKeyData().getBytes();
        byte[] pk = new byte[64];
        if (pubData.length == 64) {
            System.arraycopy(pubData, 0, pk, 0, 64);
        } else if (pubData.length == 65) {
            System.arraycopy(pubData, 1, pk, 0, 64);
        } else if (pubData.length == 66) {
            System.arraycopy(pubData, 2, pk, 0, 64);
        } else {
            return null;
        }
        return pk;
    }


    /**
     * 敏感信息密钥SM2加密
     *
     * @param pubKey 加密敏感信息密钥的公钥
     * @param encKey 敏感信息对称密钥明文
     * @return 数组 {加密且base64的敏感信息, 加密且base64的敏感信息对称密钥}
     */
    public static String SM2KeyEncrypt(String pubKey, byte[] encKey) {

        CipherParameters publicKey = getPubKeyFPubCert(pubKey);

        byte[] encKeyE = Encrypt(publicKey, encKey);

        return Base64.encodeBase64String(encKeyE);
    }


    /**
     * 敏感信息密钥SM2解密
     *
     * @param priKey   解密敏感信息的私钥
     * @param encKeyEB 敏感信息对称密钥密文
     * @return 数组 {敏感信息, 敏感信息对称密钥}
     */
    public static byte[] SM2KeyDecrypt(String priKey, String encKeyEB) {
        // EncKey: SM2
        byte[] pri = Base64.decodeBase64(priKey);
        CipherParameters privateKey = sm2PriKeyGet(Hex.encodeHexString(pri));
        byte[] encKeyE = Base64.decodeBase64(encKeyEB);
        byte[] encKey = Decrypt(privateKey, encKeyE);
        return encKey;
    }


}

